package com.uzai.console.service.log.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.uzai.common.exception.BusinessException;
import com.uzai.common.result.ResponseCode;
import com.uzai.common.utils.EsIndexName;
import com.uzai.console.common.ResponseMsg;
import com.uzai.console.common.utils.Tools;
import com.uzai.console.config.UzaiConsoleSysConfig;
import com.uzai.console.constant.ES.ESResult;
import com.uzai.console.constant.ES.ElasticsearchUtil;
import com.uzai.console.dto.log.userloginlog.UserLoginLogCleanDto;
import com.uzai.console.dto.log.userloginlog.UserLoginLogQuery;
import com.uzai.console.dto.log.userloginlog.AreaByLoginTimeMaxDto;
import com.uzai.console.entity.es.EsUserLoginLog;
import com.uzai.console.enums.SmsType;
import com.uzai.console.service.device.DeviceService;
import com.uzai.console.service.log.UserLoginLogService;
import com.uzai.console.vo.log.EsUserLoginLogTimes;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 系统登录日记查询
 * @author liuqi
 * @date 2021年08月13日 11:34
 */
@Service
public class UserLoginLogServiceImpl implements UserLoginLogService {

    Logger logger = LoggerFactory.getLogger(UserLoginLogServiceImpl.class);

    @Autowired
    private ElasticsearchUtil elasticsearchUtil;
    @Autowired
    private DeviceService deviceService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private UzaiConsoleSysConfig uzaiConsoleSysConfig;

    /**
     * 查询系统登录日记列表
     * @author liuqi
     * @date 2021/8/13 11:38
     * @param userLoginLogQuery
     * @return null
     */
    public Page<EsUserLoginLog> findUserLoginLogList(UserLoginLogQuery userLoginLogQuery){
        //新建返回到页面的LIST对象
        List<EsUserLoginLog> esUserLoginLogArrayList = new ArrayList<>();

        //执行分页查询对象
        Page<EsUserLoginLog> page = new Page<>();
        userLoginLogQuery.convert(page);

        //ES查询
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        //运营商ID
        //登录状态
        if(userLoginLogQuery.getMerId() != null) {
            boolQuery.must(QueryBuilders.termQuery("merId", Tools.getLong(userLoginLogQuery.getMerId())));
        }

        //登录状态
        if(userLoginLogQuery.getLoginStatus() != null){
            boolQuery.must(QueryBuilders.termQuery("loginStatus",userLoginLogQuery.getLoginStatus()));
        }

        //账号
        if(StringUtils.isNotBlank(userLoginLogQuery.getAccount())){
            boolQuery.must(QueryBuilders.termQuery("account",userLoginLogQuery.getAccount()));
        }

        //高亮字段
        String highlightField = null;

        //ip
        if(StringUtils.isNotBlank(userLoginLogQuery.getIp())){
            boolQuery.must(QueryBuilders.termQuery("ip",userLoginLogQuery.getIp()));
            highlightField = "ip";
        }

        searchSourceBuilder.query(boolQuery);

        //排序
        searchSourceBuilder.sort("loginTime", SortOrder.DESC);

        try {
            ESResult esResult = elasticsearchUtil.searchListData(EsIndexName.UZAI_CONSOLE_USER_LOGIN_LOG, searchSourceBuilder, userLoginLogQuery.getSize(), userLoginLogQuery.getFromIndex(), null, null, highlightField, userLoginLogQuery.getMerId());
            page.setTotal(esResult.getTotalHits());
            List<Map<String, Object>> dataList = esResult.getDataList();
            if(dataList != null && dataList.size() > 0){
                for (Map<String, Object> dataMp :dataList) {
                    EsUserLoginLog esUserLoginLog  = JSONObject.parseObject(Tools.getStrEmpty(JSONObject.toJSONString(dataMp)), EsUserLoginLog.class);
                    esUserLoginLogArrayList.add(esUserLoginLog);
                }
            }
        }catch (Exception e){
            logger.error("查询ES账号登录日志出现异常,index={},error={}"+ EsIndexName.UZAI_CONSOLE_USER_LOGIN_LOG, e.getMessage());
            //throw  new BusinessException(ResponseCode.BUSINESS_EXCEPTION, "查询失败");
        }

        //返回到页面分页对象
        page.setRecords(esUserLoginLogArrayList);
        //将分页信息复制到返回到页面的分页MO当中
        return page;
    }


    /**
     * 聚合分组查询登录次数最多的地区
     * @author liuqi
     * @date 2021/8/13 11:38
     * @param areaByLoginTimeMaxDto
     * @return null
     */
    public EsUserLoginLogTimes findAreaByLoginTimeMax(AreaByLoginTimeMaxDto areaByLoginTimeMaxDto){
        EsUserLoginLogTimes esUserLoginLogTimes = new EsUserLoginLogTimes();
        try {
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            //运营商ID
            boolQuery.must(QueryBuilders.termQuery("merId", Tools.getLong(areaByLoginTimeMaxDto.getMerId())));
            //登录成功
            boolQuery.must(QueryBuilders.termQuery("loginStatus", 1));//登录成功
            //主账号登录
            boolQuery.must(QueryBuilders.termQuery("type", 0)); //主账号登录
            //区域不为空
            boolQuery.mustNot(QueryBuilders.termQuery("city", ""));
            sourceBuilder.query(boolQuery);
            //分组聚合语句
            BucketOrder bucketOrder = BucketOrder.count(false); //这里的count方法中true表示升序排列，false代表降序排列
            AggregationBuilder aggregationBuilder = AggregationBuilders.terms("cityCount") //分组名可以随便填，但是必须和后面aggregations.get("${分组名}");里面填的分组名保持一致，否则会返回null，导致后续空指针异常
                    .script(new Script("doc['country'].value+'@@'+doc['province'].value+'@@'+doc['city'].value")) //相当于group by 3个字段，其中的@@是字段间的分隔符，最后ES返回的结果是这样的${字段1}@@${字段2}@@${字段3}@@${字段4}
                    .size(5) //聚合返回5个值（不传默认返回10个值）
                    .order(bucketOrder); //将上方的排序填入，让ES进行排序
            sourceBuilder.aggregation(aggregationBuilder); //填入分组信息
            Aggregations aggregations = elasticsearchUtil.queResultByGroup(EsIndexName.UZAI_CONSOLE_USER_LOGIN_LOG, sourceBuilder);
            Terms terms = aggregations.get("cityCount");          //填入上方terms()中相同的分组名
            if(terms != null && terms.getBuckets() != null && terms.getBuckets().size() > 0){
//                for (Terms.Bucket bucket : terms.getBuckets()) {
//                    String key = bucket.getKeyAsString();            //这里的key值是这样的：${字段1}@@${字段2}@@${字段3}@@${字段4}
//                    long count = bucket.getDocCount();               //group by后聚合统计出来的数量
//                    String[] split = key.split("@@");         //切开作为分隔符的@@，取里面需要的字段放入nameCountMap就行了
//                    nameCountMap.put(split[0], count);               //假设取第0个字段作为key
//                }
                //按照降序排序，所以只需要取第一个为登录次数最大的区域
                Terms.Bucket bucket = terms.getBuckets().get(0);
                String key = bucket.getKeyAsString();            //这里的key值是这样的：${字段1}@@${字段2}@@${字段3}@@${字段4}
                long count = bucket.getDocCount();               //group by后聚合统计出来的数量
                String[] split = key.split("@@");         //切开作为分隔符的@@
                String city =  split[2];
                esUserLoginLogTimes.setTimes(count);
                esUserLoginLogTimes.setCity(city);
            }
        } catch (Exception e) {
        }
        return esUserLoginLogTimes;
    }

    /**
     * 清空登录日志
     * @author jitwxs
     * @date 2022/12/1 14:29
     * @param userLoginLogCleanDto
     * @return boolean
     */
    public boolean userLoginLogClean(UserLoginLogCleanDto userLoginLogCleanDto){
        boolean verifyMobileCode = this.verifyMobileCode(userLoginLogCleanDto);
        if (!verifyMobileCode) {
            throw new BusinessException("验证码校验失败");
        }
        try {
            //执行删除
            DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest();
            //清空所有的发送明细
            SearchSourceBuilder searchSourceBuilder_del = new SearchSourceBuilder();
            BoolQueryBuilder boolQuery_and = new BoolQueryBuilder();
            //运营商ID
            boolQuery_and.must(QueryBuilders.termQuery("merId", Tools.getLong(userLoginLogCleanDto.getMerId())));
            //查询开始时间
            if(userLoginLogCleanDto.getQueryStartTime() != null){
                boolQuery_and.must(QueryBuilders.rangeQuery("loginTime").gte(userLoginLogCleanDto.getQueryStartTime()));
            }

            //查询结束时间
            if(userLoginLogCleanDto.getQueryEndTime() != null){
                boolQuery_and.must(QueryBuilders.rangeQuery("loginTime").lte(userLoginLogCleanDto.getQueryEndTime()));
            }
            searchSourceBuilder_del.query(boolQuery_and).size(100); //默认必须带size参数，如果超过这个参数，也会成功删除的
            deleteByQueryRequest.getSearchRequest().source(searchSourceBuilder_del);
            //删除登录日志
            elasticsearchUtil.deleteDataByQueryMulti(EsIndexName.UZAI_CONSOLE_USER_LOGIN_LOG, deleteByQueryRequest);

        } catch (Exception e) {
            logger.error("----userLoginLogCleane  is fail", e);
        }
        return true;
    }

    /**
     * 判断短信验证码是否正确
     * @author jitwxs
     * @date 2022/12/1 14:44
     * @param userLoginLogCleanDto
     * @return boolean
     */
    private boolean verifyMobileCode(UserLoginLogCleanDto userLoginLogCleanDto) {

        String mobile = userLoginLogCleanDto.getMobile();

        if(!uzaiConsoleSysConfig.getDefaultPassword().equals(userLoginLogCleanDto.getVerifyCode())){
            //获取存在redis中的验证码
            String codeByRedis = (String) redisTemplate.opsForValue().get(String.format(SmsType.CLEAN_LOGIN_LOGS.getDesc(), mobile));
            //判断验证码是否过期
            if (StringUtils.isBlank(codeByRedis)){
                throw new BusinessException(ResponseCode.BUSINESS_EXCEPTION, ResponseMsg.CODE_MISS);
            }
            //验证码是否正确
            if (!codeByRedis.equals(userLoginLogCleanDto.getVerifyCode()) && !uzaiConsoleSysConfig.getDefaultPassword().equals(userLoginLogCleanDto.getVerifyCode())){
                throw new BusinessException(ResponseCode.BUSINESS_EXCEPTION, ResponseMsg.CODE_ERR);
            }
            //验证成功，删除验证码
            redisTemplate.delete(String.format(SmsType.CLEAN_LOGIN_LOGS.getDesc(), mobile));
        }

        return true;
    }
}
